using System.Collections.Generic;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.core
{


	using NotFoundException = org.neo4j.graphdb.NotFoundException;
	using PropertyData = org.neo4j.kernel.impl.nioneo.store.PropertyData;
	using LockType = org.neo4j.kernel.impl.transaction.LockType;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;

	internal abstract class NeoPrimitive
	{
		protected internal readonly int id;
		protected internal readonly NodeManager nodeManager;

		private ArrayMap<int, PropertyData> propertyMap = null;

		protected internal abstract void changeProperty(int propertyId, object @value);

		protected internal abstract int addProperty(PropertyIndex index, object @value);

		protected internal abstract void removeProperty(int propertyId);

		protected internal abstract ArrayMap<int, PropertyData> loadProperties();

		internal NeoPrimitive(int id, NodeManager nodeManager)
		{
			this.id = id;
			this.nodeManager = nodeManager;
		}

		internal NeoPrimitive(int id, bool newPrimitive, NodeManager nodeManager)
		{
			this.id = id;
			this.nodeManager = nodeManager;
			if (newPrimitive)
			{
				propertyMap = new ArrayMap<int, PropertyData>(9, false, true);
			}
		}

		public virtual long getId()
		{
			return this.id;
		}

		public virtual IEnumerable <object> getPropertyValues()
		{
			ArrayMap<int, PropertyData> skipMap = nodeManager.getCowPropertyRemoveMap(this);
			ArrayMap<int, PropertyData> addMap = nodeManager.getCowPropertyAddMap(this);

			ensureFullProperties();
			List<object> values = new List<object>();

			foreach (int index in propertyMap.Keys)
			{
				if (skipMap != null && skipMap.get(index) != null)
				{
					continue;
				}
				if (addMap != null && addMap.get(index) != null)
				{
					continue;
				}
				values.Add(propertyMap.get(index).Value);
			}
			if (addMap != null)
			{
				foreach (PropertyData property in addMap.values())
				{
					values.Add(property.Value);
				}
			}
			return values;
		}

		public virtual IEnumerable <string> getPropertyKeys()
		{
			ArrayMap<int, PropertyData> skipMap = nodeManager.getCowPropertyRemoveMap(this);
			ArrayMap<int, PropertyData> addMap = nodeManager.getCowPropertyAddMap(this);

			ensureFullProperties();
			List<string> keys = new List<string>();

			foreach (int index in propertyMap.Keys)
			{
				if (skipMap != null && skipMap.get(index) != null)
				{
					continue;
				}
				if (addMap != null && addMap.get(index) != null)
				{
					continue;
				}
				keys.Add(nodeManager.getIndexFor(index).Key);
			}
			if (addMap != null)
			{
				foreach (int index in addMap.Keys)
				{
					keys.Add(nodeManager.getIndexFor(index).Key);
				}
			}
			return keys;
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public Object getProperty(String key) throws NotFoundException
		public virtual object getProperty(string key)
		{
			if (key == null)
			{
				throw new IllegalArgumentException("null key");
			}
			ArrayMap<int, PropertyData> skipMap = nodeManager.getCowPropertyRemoveMap(this);
			ArrayMap<int, PropertyData> addMap = nodeManager.getCowPropertyAddMap(this);

			ensureFullProperties();
			foreach (PropertyIndex index in nodeManager.index(key))
			{
				if (skipMap != null && skipMap.get(index.getKeyId()) != null)
				{
					throw newPropertyNotFoundException(key);
				}
				if (addMap != null)
				{
					PropertyData property = addMap.get(index.getKeyId());
					if (property != null)
					{
						return getPropertyValue(property);
					}
				}
				PropertyData property = propertyMap.get(index.getKeyId());
				if (property != null)
				{
					return getPropertyValue(property);
				}
			}
			PropertyData property = getSlowProperty(addMap, skipMap, key);
			if (property != null)
			{
				return getPropertyValue(property);
			}
			throw newPropertyNotFoundException(key);
		}

		private NotFoundException newPropertyNotFoundException(string key)
		{
			return new NotFoundException(key + " property not found for " + this + ".");
		}

		private PropertyData getSlowProperty(ArrayMap<int, PropertyData> addMap, ArrayMap<int, PropertyData> skipMap, string key)
		{
			if (nodeManager.hasAllPropertyIndexes())
			{
				return null;
			}
			if (addMap != null)
			{
				foreach (int keyId in addMap.Keys)
				{
					if (!nodeManager.hasIndexFor(keyId))
					{
						PropertyIndex indexToCheck = nodeManager.getIndexFor(keyId);
						if (indexToCheck.Key.Equals(key))
						{
							if (skipMap != null && skipMap.get(keyId) != null)
							{
								throw newPropertyNotFoundException(key);
							}
							PropertyData property = addMap.get(indexToCheck.getKeyId());
							if (property != null)
							{
								return property;
							}
						}
					}
				}
			}
			foreach (int keyId in propertyMap.Keys)
			{
				if (!nodeManager.hasIndexFor(keyId))
				{
					PropertyIndex indexToCheck = nodeManager.getIndexFor(keyId);
					if (indexToCheck.Key.Equals(key))
					{
						if (skipMap != null && skipMap.get(keyId) != null)
						{
							throw newPropertyNotFoundException(key);
						}
						PropertyData property = propertyMap.get(indexToCheck.getKeyId());
						if (property != null)
						{
							return property;
						}
					}
				}
			}
			return null;
		}

		public virtual object getProperty(string key, object defaultValue)
		{
			if (key == null)
			{
				throw new IllegalArgumentException("null key");
			}
			ArrayMap<int, PropertyData> skipMap = nodeManager.getCowPropertyRemoveMap(this);
			ArrayMap<int, PropertyData> addMap = nodeManager.getCowPropertyAddMap(this);

			ensureFullProperties();
			foreach (PropertyIndex index in nodeManager.index(key))
			{
				if (skipMap != null && skipMap.get(index.getKeyId()) != null)
				{
					return defaultValue;
				}
				if (addMap != null)
				{
					PropertyData property = addMap.get(index.getKeyId());
					if (property != null)
					{
						return getPropertyValue(property);
					}
				}
				PropertyData property = propertyMap.get(index.getKeyId());
				if (property != null)
				{
					return getPropertyValue(property);
				}
			}
			PropertyData property = getSlowProperty(addMap, skipMap, key);
			if (property != null)
			{
				return getPropertyValue(property);
			}
			return defaultValue;
		}

		public virtual bool hasProperty(string key)
		{
			if (key == null)
			{
				return false;
			}

			ArrayMap<int, PropertyData> skipMap = nodeManager.getCowPropertyRemoveMap(this);
			ArrayMap<int, PropertyData> addMap = nodeManager.getCowPropertyAddMap(this);

			ensureFullProperties();
			foreach (PropertyIndex index in nodeManager.index(key))
			{
				if (skipMap != null && skipMap.get(index.getKeyId()) != null)
				{
					return false;
				}
				if (addMap != null)
				{
					PropertyData property = addMap.get(index.getKeyId());
					if (property != null)
					{
						return true;
					}
				}
				PropertyData property = propertyMap.get(index.getKeyId());
				if (property != null)
				{
					return true;
				}
			}
			PropertyData property = getSlowProperty(addMap, skipMap, key);
			if (property != null)
			{
				return true;
			}
			return false;
		}

		public virtual void setProperty(string key, object @value)
		{
			if (key == null || @value == null)
			{
				throw new IllegalArgumentException("Null parameter, " + "key=" + key + ", " + "value=" + @value);
			}
			nodeManager.acquireLock(this, LockType.WRITE);
			bool success = false;
			try
			{
				ensureFullProperties();
				ArrayMap<int, PropertyData> addMap = nodeManager.getCowPropertyAddMap(this, true);
				ArrayMap<int, PropertyData> skipMap = nodeManager.getCowPropertyRemoveMap(this);
				PropertyIndex index = null;
				PropertyData property = null;
				bool foundInSkipMap = false;
				foreach (PropertyIndex cachedIndex in nodeManager.index(key))
				{
					if (skipMap != null)
					{
						if (skipMap.Remove(cachedIndex.getKeyId()) != null)
						{
							foundInSkipMap = true;
						}
					}
					index = cachedIndex;
					property = addMap.get(cachedIndex.getKeyId());
					if (property != null)
					{
						break;
					}
					property = propertyMap.get(cachedIndex.getKeyId());
					if (property != null)
					{
						break;
					}
				}
				if (property == null && !nodeManager.hasAllPropertyIndexes())
				{
					foreach (int keyId in addMap.Keys)
					{
						if (!nodeManager.hasIndexFor(keyId))
						{
							PropertyIndex indexToCheck = nodeManager.getIndexFor(keyId);
							if (indexToCheck.Key.Equals(key))
							{
								if (skipMap != null)
								{
									skipMap.Remove(indexToCheck.getKeyId());
								}
								index = indexToCheck;
								property = addMap.get(indexToCheck.getKeyId());
								if (property != null)
								{
									break;
								}
							}
						}
					}
					if (property == null)
					{
						foreach (int keyId in propertyMap.Keys)
						{
							if (!nodeManager.hasIndexFor(keyId))
							{
								PropertyIndex indexToCheck = nodeManager.getIndexFor(keyId);
								if (indexToCheck.Key.Equals(key))
								{
									if (skipMap != null)
									{
										skipMap.Remove(indexToCheck.getKeyId());
									}
									index = indexToCheck;
									property = propertyMap.get(indexToCheck.getKeyId());
									if (property != null)
									{
										break;
									}
								}
							}
						}
					}
				}
				if (index == null)
				{
					index = nodeManager.createPropertyIndex(key);
				}
				if (property != null && !foundInSkipMap)
				{
					int propertyId = property.getId();
					changeProperty(propertyId, @value);
					property = new PropertyData(propertyId, @value);
				}
				else
				{
					int propertyId = addProperty(index, @value);
					property = new PropertyData(propertyId, @value);
				}
				addMap.put(index.getKeyId(), property);
				success = true;
			}
			finally
			{
				nodeManager.releaseLock(this, LockType.WRITE);
				if (!success)
				{
					setRollbackOnly();
				}
			}
		}

		public virtual object removeProperty(string key)
		{
			if (key == null)
			{
				throw new IllegalArgumentException("Null parameter.");
			}
			nodeManager.acquireLock(this, LockType.WRITE);
			bool success = false;
			try
			{
				ensureFullProperties();
				PropertyData property = null;
				ArrayMap<int, PropertyData> addMap = nodeManager.getCowPropertyAddMap(this);
				ArrayMap<int, PropertyData> removeMap = nodeManager.getCowPropertyRemoveMap(this, true);
				foreach (PropertyIndex cachedIndex in nodeManager.index(key))
				{
					if (addMap != null)
					{
						property = addMap.Remove(cachedIndex.getKeyId());
						if (property != null)
						{
							removeMap.put(cachedIndex.getKeyId(), property);
							break;
						}
					}
					if (removeMap.get(cachedIndex.getKeyId()) != null)
					{
						success = true;
						return null;
					}
					property = propertyMap.get(cachedIndex.getKeyId());
					if (property != null)
					{
						removeMap.put(cachedIndex.getKeyId(), property);
						break;
					}
				}
				if (property == null && !nodeManager.hasAllPropertyIndexes())
				{
					if (addMap != null)
					{
						foreach (int keyId in addMap.Keys)
						{
							if (!nodeManager.hasIndexFor(keyId))
							{
								PropertyIndex indexToCheck = nodeManager.getIndexFor(keyId);
								if (indexToCheck.Key.Equals(key))
								{
									property = addMap.Remove(indexToCheck.getKeyId());
									if (property != null)
									{
										removeMap.put(indexToCheck.getKeyId(), property);
										break;
									}
								}
							}
						}
						if (property == null)
						{
							foreach (int keyId in propertyMap.Keys)
							{
								if (!nodeManager.hasIndexFor(keyId))
								{
									PropertyIndex indexToCheck = nodeManager.getIndexFor(keyId);
									if (indexToCheck.Key.Equals(key))
									{
										property = propertyMap.get(indexToCheck.getKeyId());
										if (property != null)
										{
											removeMap.put(indexToCheck.getKeyId(), property);
											break;
										}
									}
								}
							}
						}
					}
				}
				if (property == null)
				{
					success = true;
					return null;
				}
				removeProperty(property.getId());
				success = true;
				return getPropertyValue(property);
			}
			finally
			{
				nodeManager.releaseLock(this, LockType.WRITE);
				if (!success)
				{
					setRollbackOnly();
				}
			}
		}

		private object getPropertyValue(PropertyData property)
		{
			object @value = property.Value;
			if (@value == null)
			{
				@value = nodeManager.loadPropertyValue(property.getId());
				property.setNewValue(@value);
			}
			return @value;
		}

		protected internal virtual void commitPropertyMaps(ArrayMap<int, PropertyData> cowPropertyAddMap, ArrayMap<int, PropertyData> cowPropertyRemoveMap)
		{
			if (propertyMap == null)
			{
			// we will load full in some other tx
				return;
			}
			if (cowPropertyAddMap != null)
			{
				foreach (int index in cowPropertyAddMap.Keys)
				{
					propertyMap.put(index, cowPropertyAddMap.get(index));
				}
			}
			if (cowPropertyRemoveMap != null && propertyMap != null)
			{
				foreach (int index in cowPropertyRemoveMap.Keys)
				{
					propertyMap.Remove(index);
				}
			}
		}

		private bool ensureFullProperties()
		{
			if (propertyMap == null)
			{
				this.propertyMap = loadProperties();
				return true;
			}
			return false;
		}

		protected internal virtual void setRollbackOnly()
		{
			nodeManager.setRollbackOnly();
		}
	}
}